package com.king.alg;

import com.king.pojo.Point;

/**
 * 几何算法类
 */
public class GeometryAlgorithms {

    /**
     * 求两点的欧式距离
     *
     * @param p1 p1
     * @param p2 p2
     * @return 距离
     */
    public static double norm(Point p1, Point p2) {
        return Math.sqrt(Math.pow(p1.getX() - p2.getX(), 2) + Math.pow(p1.getY() - p2.getY(), 2));
    }

    /**
     * 得到向量
     *
     * @param p1 始点
     * @param p2 终点
     * @return 向量
     */
    public static double[] getVector(Point p1, Point p2) {
        return new double[]{p2.getX() - p1.getX(), p2.getY() - p1.getY()};
    }

    /**
     * 两向量的内积
     *
     * @param p1 第1个向量的始点
     * @param p2 第1个向量的终点
     * @param p3 第2个向量的始点
     * @param p4 第2个向量的终点
     * @return 内积
     */
    public static double innerProduct(Point p1, Point p2, Point p3, Point p4) {
        double[] v1 = getVector(p1, p2);
        double[] v2 = getVector(p3, p4);

        return v1[0] * v2[0] + v1[1] * v2[1];
    }

    /**
     * 两向量的外积的长度，带正负
     *
     * @param p1 第1个向量的始点
     * @param p2 第1个向量的终点
     * @param p3 第2个向量的始点
     * @param p4 第2个向量的终点
     * @return 外积
     */
    public static double outerProduct(Point p1, Point p2, Point p3, Point p4) {
        double[] v1 = getVector(p1, p2);
        double[] v2 = getVector(p3, p4);

        return v1[0] * v2[1] - v2[0] * v1[1];
    }

    /**
     * 得到距离直线或线段距离最短的点
     *
     * @param A             线段端点
     * @param B             线段端点
     * @param C             点
     * @param isLineSegment 是否是线段
     * @return 点
     */
    public static Point minDistancePoint2LineOrLineSegment(Point A, Point B, Point C, boolean isLineSegment) {
        double AB = Math.pow(A.getX() - B.getX(), 2) + Math.pow(A.getY() - B.getY(), 2);
        double BABC = GeometryAlgorithms.innerProduct(B, A, B, C);
        double lambda = BABC / AB;

        if (isLineSegment && (lambda < 0 || lambda > 1)) {
            return GeometryAlgorithms.norm(A, C) < GeometryAlgorithms.norm(B, C) ? A : B;
        }

        return new Point(lambda * A.getX() + (1 - lambda) * B.getX(), lambda * A.getY() + (1 - lambda) * B.getY());
    }

    /**
     * 点 C 到线段 AB 或直线 AB 的最短距离
     *
     * @param A             线段端点
     * @param B             线段端点
     * @param C             点
     * @param isLineSegment 是否是线段
     * @return 最短距离
     */
    public static double minDistance2LineOrLineSegment(Point A, Point B, Point C, boolean isLineSegment) {
        return GeometryAlgorithms.norm(C, minDistancePoint2LineOrLineSegment(A, B, C, isLineSegment));
    }

    /**
     * 改进最小二乘法，距离和最短
     *
     * @param data 点列
     * @return {b, k}
     */
    public static double[] linearFit(double[][] data) {
        double avgX, avgY, avgXY, avgX2_Y2, k;
        double sumX = 0, sumY = 0, sumXY = 0, sumX2_Y2 = 0;

        for (double[] datum : data) {
            double x = datum[0];
            double y = datum[1];
            double x2 = Math.pow(x, 2);
            double y2 = Math.pow(y, 2);
            double xy = x * y;
            sumX += x;
            sumY += y;
            sumXY += xy;
            sumX2_Y2 += x2 - y2;
        }

        avgX = sumX / data.length;
        avgY = sumY / data.length;
        avgXY = sumXY / data.length;
        avgX2_Y2 = sumX2_Y2 / data.length;

        double a = avgXY - avgX * avgY;
        double b = avgX2_Y2 - Math.pow(avgX, 2) + Math.pow(avgY, 2);
        double c = avgX * avgY - avgXY;

        k = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a);

        return new double[]{avgY - k * avgX, k};
    }

    /**
     * 计算两条直线的交点坐标
     *
     * @param bk1 bk1
     * @param bk2 bk2
     * @return 交点坐标
     */
    public static double[] getIntersectPointByTwoLines(double[] bk1, double[] bk2) {
        double b1 = bk1[0], k1 = bk1[1], b2 = bk2[0], k2 = bk2[1];
        double x = (b2 - b1) / (k1 - k2);
        return new double[]{x, k1 * x + b1};
    }

}
